#include "Ogre7zArchive.h"

#include "Ogre7zCommon.h"
#include "Ogre7zFile.h"

#include <OgreLogManager.h>

namespace Ogre
{

//-------------------------------------------------------------------------------------------------------------------------------------------------
SevenZipArchive::SevenZipArchive( const String& name, const String& archType )
: Archive(name, archType)
, mInitialised(false)
, m7ZipFile(NULL)
{
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
SevenZipArchive::~SevenZipArchive()
{
	// Unload archive
	unload();
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
bool SevenZipArchive::isCaseSensitive() const
{
	return false;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void SevenZipArchive::load()
{
	// Lock mutex
	OGRE_LOCK_AUTO_MUTEX

	// Is archive initialised?
	if( mInitialised )
		return;

	m7ZipFile = OGRE_NEW_T( SevenZipFile, MEMCATEGORY_GENERAL )( mName );

	try
	{
		// Load archive
		m7ZipFile->load();
	}
	catch(...)
	{
		// Destroy 7z file instance
		OGRE_DELETE_T( m7ZipFile, SevenZipFile, MEMCATEGORY_GENERAL );
		// re-throw
		throw;
	}

	size_t numFiles = m7ZipFile->getNumFiles();

	// Itarate all files
	for( size_t index = 0; index < numFiles; index++ )
	{
		FileInfo info;
		info.archive = this;
		// Get file name
		m7ZipFile->getFileName( index, info.filename );
		// Get basename / path
		StringUtil::splitFilename( info.filename, info.basename, info.path );
		// Get sizes
		info.compressedSize = m7ZipFile->getIsDirectory( index ) ? size_t(-1) : 0;
		info.uncompressedSize = m7ZipFile->getFileSize( index );

		mFileList.push_back( info );
	}

	mInitialised = true;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
void SevenZipArchive::unload()
{
	// Lock mutex
	OGRE_LOCK_AUTO_MUTEX

	// Is archive initialised?
	if( !mInitialised )
		return;

	// Destroy 7z file instance
	OGRE_DELETE_T( m7ZipFile, SevenZipFile, MEMCATEGORY_GENERAL );

	// Crear archive file list
	mFileList.clear();

	mInitialised = false;

}

//-------------------------------------------------------------------------------------------------------------------------------------------------
DataStreamPtr SevenZipArchive::open(const String& filename, bool readOnly) const
{
	OGRE_LOCK_AUTO_MUTEX

	// Is archive initialised?
	if( !mInitialised )
		return DataStreamPtr();


	// Retrieve file index
	size_t index = getFileIndex( filename );
	if( index == size_t(-1) )
	{
		LogManager::getSingleton().stream() << "Unable to find file '" << filename << "', at 7zip archive '" << mName << "'";
		return DataStreamPtr();
	}

	size_t offset = 0;
	size_t outSizeProcessed = 0;

	// Extract file to memory buffer
	SRes res = m7ZipFile->extract( index, offset, outSizeProcessed );

	// Is successfull?
	if( res != SZ_OK )
	{
		LogManager::getSingleton().stream() << "Unable to open file archive '" << filename
			<< "', from archive '" << mName <<"', error: " << SevenZipUtil::getErrorString( res );
		return DataStreamPtr();
	}

	// Construct & return stream
	void* buffer = (void*)OGRE_ALLOC_T( uchar, outSizeProcessed, MEMCATEGORY_GENERAL );
	memcpy( buffer, m7ZipFile->getOutBuffer() + offset, outSizeProcessed );
	return DataStreamPtr( OGRE_NEW_T( MemoryDataStream, MEMCATEGORY_GENERAL )
		( filename, buffer, outSizeProcessed, true, true ), SPFM_DELETE_T );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
StringVectorPtr SevenZipArchive::list(bool recursive, bool dirs)
{
	OGRE_LOCK_AUTO_MUTEX

	StringVectorPtr ret = StringVectorPtr( OGRE_NEW_T( StringVector, MEMCATEGORY_GENERAL )(), SPFM_DELETE_T );

	for( FileInfoList::iterator it = mFileList.begin(); it != mFileList.end(); it++ )
	{
		if( (dirs == (it->compressedSize == size_t (-1))) && (recursive || it->path.empty()) )
		{
			ret->push_back(it->filename);
		}
	}
	
	return ret;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
FileInfoListPtr SevenZipArchive::listFileInfo(bool recursive, bool dirs)
{
	OGRE_LOCK_AUTO_MUTEX
		
	FileInfoList* fil = OGRE_NEW_T( FileInfoList, MEMCATEGORY_GENERAL )();

	for( FileInfoList::iterator it = mFileList.begin(); it != mFileList.end(); it++ )
	{
		if( (dirs == (it->compressedSize == size_t (-1))) && (recursive || it->path.empty()) )
		{
			fil->push_back(*it);
		}
	}
	
	return FileInfoListPtr( fil, SPFM_DELETE_T );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
StringVectorPtr SevenZipArchive::find(const String& pattern, bool recursive, bool dirs)
{
	OGRE_LOCK_AUTO_MUTEX
	StringVectorPtr ret = StringVectorPtr( OGRE_NEW_T( StringVector, MEMCATEGORY_GENERAL )(), SPFM_DELETE_T );

	// If pattern contains a directory name, do a full match
	 bool full_match = (pattern.find ('/') != String::npos) || (pattern.find ('\\') != String::npos);
	 
	 for( FileInfoList::iterator it = mFileList.begin(); it != mFileList.end(); it++ )
	 {
		 if( ( dirs == (it->compressedSize == size_t (-1))) && (recursive || full_match || it->path.empty()))
		 {
			 // Check basename matches pattern (zip is case insensitive)
			 if( StringUtil::match( full_match ? it->filename : it->basename, pattern, false ) )
			 {
				 ret->push_back( it->filename );
			 }
		 }
	 }
	 
	 return ret;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
bool SevenZipArchive::exists(const String& filename)
{
	OGRE_LOCK_AUTO_MUTEX
	// Itarate all files
	for( FileInfoList::iterator it = mFileList.begin(); it != mFileList.end(); it++ )
	{
		if( it->filename == filename )
			return true;
	}

	return false;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
time_t SevenZipArchive::getModifiedTime(const String& filename)
{
	OGRE_LOCK_AUTO_MUTEX

	// Is archive initialised?
	if( !mInitialised )
		return 0;

	// Retrieve file index
	size_t index = getFileIndex( filename );
	if( index == size_t(-1) )
	{
		LogManager::getSingleton().stream() << "Unable to find file '" << filename << "', at 7zip archive '" << mName << "'";
		return 0;
	}
	
	return m7ZipFile->getModifiedTime( index );
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
FileInfoListPtr SevenZipArchive::findFileInfo(const String& pattern, bool recursive, bool dirs)
{
	OGRE_LOCK_AUTO_MUTEX
	FileInfoListPtr ret = FileInfoListPtr( OGRE_NEW_T( FileInfoList, MEMCATEGORY_GENERAL )(), SPFM_DELETE_T );

    // If pattern contains a directory name, do a full match
    bool full_match = (pattern.find ('/') != String::npos) || (pattern.find ('\\') != String::npos);

    for( FileInfoList::iterator it = mFileList.begin(); it != mFileList.end(); it++ )
	{
        if( (dirs == (it->compressedSize == size_t (-1))) && (recursive || full_match || it->path.empty()))
		{
            // Check name matches pattern (zip is case insensitive)
            if( StringUtil::match( full_match ? it->filename : it->basename, pattern, false ) )
			{
				ret->push_back(*it);
			}
		}
	}

	return ret;
}

//-------------------------------------------------------------------------------------------------------------------------------------------------
size_t SevenZipArchive::getFileIndex( const String& name ) const
{
	OGRE_LOCK_AUTO_MUTEX
	// Is archive initialised?
	if( !mInitialised )
		return size_t(-1);

	// Itarate all files
	size_t index = 0;
    for( FileInfoList::const_iterator it = mFileList.begin(); it != mFileList.end(); it++ )
	{
		if( it->filename == name )
			return index;

		index++;
	}

	return size_t(-1);
}

} // end of namespace Ogre
